Template method pattern

模板方法模式

前面的内容都是关于封装的,包括封装对象创建、方法掉有那个、复杂接口等。
这里介绍的模板方法模式同样是用于封装,封装的对象是算法块,使得子类可以任何时候将自己挂进运算里,无需重写算法流程。

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新插入新的算法实现。

模板就是一个方法,更具体地说,这个方法将算法定义一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这样可以确保算法的结构保持不变,同时由子类提供部分实现。

Design_pattern_templatemethod_1

Design_pattern_templatemethod_2

Design_pattern_templatemethod_3

注意到上面的类提供了一个钩子,钩子是一种被声明在抽象类中的方法,但是只有空的或默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,子类也可以不选择挂钩。

Design_pattern_templatemethod_4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class CaffeineBeverageWithHook {

void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}

abstract void brew();

abstract void addCondiments();

void boilWater() {
System.out.println("Boiling water");
}

void pourInCup() {
System.out.println("Pouring into cup");
}

boolean customerWantsCondiments() {
return true;
}
}

为了使用钩子,我们可以在子类覆盖它。在上面的例子中,钩子用来决定是否执行模板方法的某部分算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class CoffeeWithHook extends CaffeineBeverageWithHook {

public void brew() {
System.out.println("Dripping Coffee through filter");
}

public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}

public boolean customerWantsCondiments() {

String answer = getUserInput();

if (answer.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}

private String getUserInput() {
String answer = null;

System.out.print("Would you like milk and sugar with your coffee (y/n)? ");

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException ioe) {
System.err.println("IO error trying to read your answer");
}
if (answer == null) {
return "no";
}
return answer;
}
}

模板方法同样应用了一个设计原则:好莱坞原则。
别调用我们,我们会调用你。

好莱坞原则可以防止依赖腐败。当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件有依赖边侧组件,而边侧组件有依赖底层组件时,依赖腐败就产生了。

在好莱坞原则下,我们允许底层组件将自己挂钩到系统上,但是有高层组件巨鼎什么时候和怎样使用这些底层组件。换句话说,也就是,高层组件对待低层组件的方式是”别调用我们,我们会调用你”。

Design_pattern_templatemethod_5

当我们设计模板方法模式时,我们告诉子,”不要调用我们,我们会调用你。”

Design_pattern_templatemethod_6

在Java中一个实际的例子是sort()方法的实现,sort原则上允许对任何类型的数组进行排序,只要该类实现了compareTo()方法,也就是在sort中使用模板方法,在实际使用中调用相应类的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Duck implements Comparable {
String name;
int weight;

public Duck(String name, int weight) {
this.name = name;
this.weight = weight;
}

public String toString() {
return name + " weighs " + weight;
}



public int compareTo(Object object) {

Duck otherDuck = (Duck)object;

if (this.weight < otherDuck.weight) {
return -1;
} else if (this.weight == otherDuck.weight) {
return 0;
} else { // this.weight > otherDuck.weight
return 1;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class DuckSortTestDrive {

public static void main(String[] args) {
Duck[] ducks = {
new Duck("Daffy", 8),
new Duck("Dewey", 2),
new Duck("Howard", 7),
new Duck("Louie", 2),
new Duck("Donald", 10),
new Duck("Huey", 2)
};

System.out.println("Before sorting:");
display(ducks);

Arrays.sort(ducks);

System.out.println("\nAfter sorting:");
display(ducks);
}

public static void display(Duck[] ducks) {
for (int i = 0; i < ducks.length; i++) {
System.out.println(ducks[i]);
}
}
}

模板方法、策略、工厂方法比较:

  • 模板方法:子类决定如何实现算法中的某些步骤。
  • 策略:封装可互换的行为,然后使用委托来决定要采用哪一个行为。
  • 工厂方法:由子类决定实例化哪个具体类。